package com.xhj.mybatis.reflection;

import com.xhj.mybatis.annotations.Param;
import com.xhj.mybatis.binding.MapperMethod.ParamMap;
import com.xhj.mybatis.session.Configuration;
import com.xhj.mybatis.session.ResultHandler;
import com.xhj.mybatis.session.RowBounds;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;

/**
 * ParamNameResolver
 *
 * @author XJks
 * @description ParamNameResolver 类用于解析方法参数的名称，主要用于 MyBatis 框架中 SQL 参数的映射
 */
public class ParamNameResolver {

    /**
     * 定义通用参数名前缀，比如 param1、param2 等
     */
    public static final String GENERIC_NAME_PREFIX = "param";

    /**
     * 标记是否使用真实的参数名（如编译时保留了参数名则为 true，否则为 false）
     */
    private final boolean useActualParamName;

    /**
     * <p>
     * 该成员变量用于存储方法参数的索引和参数名之间的有序映射关系，保证顺序一致且不可修改。<br />
     * 索引是参数在方法声明中的位置，参数名可以通过 {@link Param} 注解指定，
     * 如果没有指定，则使用参数的顺序编号（如"0"、"1"等）。<br />
     * </p>
     * <ul>
     * <li>aMethod(@Param("M") int a, @Param("N") int b) -&gt; {{0, "M"}, {1, "N"}} // 有注解，参数名为注解值</li>
     * <li>aMethod(int a, int b) -&gt; {{0, "0"}, {1, "1"}} // 无注解，参数名为顺序编号</li>
     * </ul>
     */
    private final SortedMap<Integer, String> names;

    /**
     * 标记方法参数上是否存在 @Param 注解
     */
    private boolean hasParamAnnotation;

    /**
     * 构造函数，解析方法参数并建立索引与参数名的映射关系。
     *
     * @param config MyBatis 全局配置对象
     * @param method 要解析参数的 Java 方法
     */
    public ParamNameResolver(Configuration config, Method method) {
        // 记录是否使用真实参数名（比如编译参数保留了参数名时可以用）
        this.useActualParamName = config.isUseActualParamName();
        // 获取方法的所有参数类型
        final Class<?>[] paramTypes = method.getParameterTypes();
        // 获取方法的所有参数上的注解
        final Annotation[][] paramAnnotations = method.getParameterAnnotations();
        // 用于存储参数索引和参数名的映射，按索引排序
        final SortedMap<Integer, String> map = new TreeMap<>();
        // 参数个数
        int paramCount = paramAnnotations.length;
        // 遍历每一个参数
        for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
            // 判断是否是特殊参数（如RowBounds或ResultHandler），如果是则跳过
            if (isSpecialParameter(paramTypes[paramIndex])) {
                // 跳过特殊参数
                continue;
            }
            // 参数名，初始为null
            String name = null;
            // 遍历该参数上的所有注解
            for (Annotation annotation : paramAnnotations[paramIndex]) {
                // 如果有@Param注解
                if (annotation instanceof Param) {
                    // 标记有@Param注解
                    hasParamAnnotation = true;
                    // 获取@Param注解的值作为参数名
                    name = ((Param) annotation).value();
                    break;
                }
            }
            // 如果没有@Param注解
            if (name == null) {
                // 如果配置允许，尝试获取真实参数名
                if (useActualParamName) {
                    name = getActualParamName(method, paramIndex);
                }
                // 如果还是没有获取到参数名
                if (name == null) {
                    // 用参数在map中的顺序作为参数名（如"0"、"1"等）
                    name = String.valueOf(map.size());
                }
            }
            // 将参数索引和参数名放入map
            map.put(paramIndex, name);
        }
        // 将map变为不可修改的有序map，赋值给成员变量names
        names = Collections.unmodifiableSortedMap(map);
    }

    /**
     * 获取方法参数的实际名称（如编译时保留了参数名，则可以获取到真实的参数名）
     *
     * @param method     方法对象
     * @param paramIndex 参数索引
     * @return 实际参数名
     */
    private String getActualParamName(Method method, int paramIndex) {
        // 调用 ParamNameUtil 工具类获取方法的所有参数名列表，然后取出指定索引的参数名
        return ParamNameUtil.getParamNames(method).get(paramIndex);
    }

    /**
     * 判断参数类型是否为特殊参数（RowBounds 或 ResultHandler），这些参数在 MyBatis 中有特殊用途
     *
     * @param clazz 参数类型
     * @return 是否为特殊参数
     */
    private static boolean isSpecialParameter(Class<?> clazz) {
        // 如果 clazz 是 RowBounds 或其子类，或者是 ResultHandler 或其子类，则返回 true，否则返回 false
        return RowBounds.class.isAssignableFrom(clazz) || ResultHandler.class.isAssignableFrom(clazz);
    }

    /**
     * 返回 SQL 提供者引用的参数名数组。
     *
     * @return 参数名数组
     */
    public String[] getNames() {
        // 将 names 映射中的所有参数名（value）转换为字符串数组并返回
        return names.values().toArray(new String[0]);
    }

    /**
     * <p>
     * 如果只有一个非特殊参数，则直接返回该参数本身，不加名字。
     * 如果有多个参数，则会为每个参数命名，并且除了默认名字外，还会添加通用名字（param1、param2等）。
     * </p>
     * TODO:该方法很重要，会对 XxxMapper.class 中的方法实际入参进行包装处理，后续在 DefaultParameterHandler 中会用到。
     *
     * @param args 方法实际传入的参数数组
     * @return 命名后的参数对象（单个参数时可能直接返回参数本身，多个参数时返回Map）
     */
    public Object getNamedParams(Object[] args) {
        // 获取参数个数（排除特殊参数后的）
        final int paramCount = names.size();
        // 如果参数为null或没有参数，直接返回null
        if (args == null || paramCount == 0) {
            return null;
        } else if (!hasParamAnnotation && paramCount == 1) {
            // TODO:处理单个参数的情况，从 args 中取出唯一参数返回，去一层包装。
            // 如果没有@Param注解且只有一个参数，直接返回该参数（如有需要会包一层Map）
            // 取出唯一参数的值
            Object value = args[names.firstKey()];
            // 如果参数是集合或数组，包一层Map，key为参数名或null
            return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
        } else {
            // TODO:处理多个参数的情况，返回一个 Map 对象。
            // 有多个参数或有@Param注解，创建一个Map存放参数名和参数值
            final Map<String, Object> param = new ParamMap<>();
            int i = 0;
            // 遍历所有参数索引和参数名
            for (Map.Entry<Integer, String> entry : names.entrySet()) {
                // 用参数名作为key，参数值作为value放入Map
                param.put(entry.getValue(), args[entry.getKey()]);
                // 生成通用参数名（如param1、param2等）
                final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
                // 如果通用参数名没有被@Param注解占用，则也放入Map
                if (!names.containsValue(genericParamName)) {
                    param.put(genericParamName, args[entry.getKey()]);
                }
                i++;
            }
            // 返回包含所有参数的Map
            return param;
        }
    }

    /**
     * 如果传入的对象是 Collection（集合）或数组，则将其包装到 ParamMap 中返回。
     * 这样做的目的是让 MyBatis 能够更方便地处理集合或数组类型的参数。
     *
     * @param object          传入的参数对象，可能是集合、数组或其他类型
     * @param actualParamName 实际参数名（如果指定了，会以该名字存入 ParamMap）
     * @return 如果是集合或数组，返回包装后的 ParamMap，否则原样返回 object
     */
    public static Object wrapToMapIfCollection(Object object, String actualParamName) {
        // 如果 object 是 Collection 类型（如 List、Set 等）
        if (object instanceof Collection) {
            // 创建一个新的 ParamMap，用于存放参数
            ParamMap<Object> map = new ParamMap<>();
            // 将集合对象以 "collection" 作为 key 存入 map
            map.put("collection", object);
            // 如果 object 还是 List 类型，再以 "list" 作为 key 存入 map
            if (object instanceof List) {
                map.put("list", object);
            }
            // 如果 actualParamName 不为 null，也以该名字存入 map
            Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
            // 返回包装好的 map
            return map;
            // 如果 object 不是 null 且是数组类型
        } else if (object != null && object.getClass().isArray()) {
            // 创建一个新的 ParamMap
            ParamMap<Object> map = new ParamMap<>();
            // 将数组对象以 "array" 作为 key 存入 map
            map.put("array", object);
            // 如果 actualParamName 不为 null，也以该名字存入 map
            Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
            // 返回包装好的 map
            return map;
        }
        // 如果既不是集合也不是数组，直接返回原始对象
        return object;
    }

}

